﻿namespace Moly.misc
{
    using System;
    using System.Diagnostics;
    using System.IO;
    using System.Reflection;
    using System.Runtime.InteropServices;
    using System.Runtime.InteropServices.ComTypes;

    [Serializable]       
    public class ModuleFile
    {
        public ModuleFile()
        {
        }

        private string _hash;
        private ModuleType _type;
        public ModuleType BinaryType
        {
            get
            {

                if (_type == ModuleType.NotSet)
                    _type = getBinaryType();
                return _type;
            }
        }
            
        public ModuleFile(string fileAndRes)
        {
            Raw = fileAndRes;
            if (Raw.Contains("%"))
                Raw = Environment.ExpandEnvironmentVariables(Raw);
            _type = ModuleType.NotSet;
        }
        public string Version
        {
            get { return GetVersionFromFile(ValidPath); }
        }

        public static string GetVersionFromFile(string path)
        {
            var fvi = FileVersionInfo.GetVersionInfo(path);
            return String.Format("{0}.{1}.{2}.{3}", fvi.FileMajorPart, fvi.FileMinorPart, fvi.FileBuildPart, fvi.FilePrivatePart);
        }

        public DateTime Date
        {
            get { return File.GetLastWriteTime(ValidPath); }
        }

        public string Hash
        {
            get
            {
                if (String.IsNullOrEmpty(_hash))
                    _hash = Utility.ComputeHash(ValidPath);
                return _hash;
            }
        }

        public void VerifyExist()
        {
            if (!File.Exists(ValidPath))
            {
                throw new Exception("file for module not found @ " + Raw);
            }
        }

        public bool Registerable
        {
            get
            {
                return BinaryType == ModuleType.Com || BinaryType == ModuleType.Il;
            }
        }
        public string Win32Name
        {
            get
            {
                var spl = Raw.Split('\\');
                var rs = spl[spl.Length - 1];
                int temp;
                if (Int32.TryParse(rs, out temp))
                {
                    var f = spl[spl.Length - 2];
                    return String.Format("{0}\\{1}", f, rs);
                }
                return rs;
            }
        }

        public string Raw { get; private set; }
        public string ValidPath
        {
            get { return Win32Name.Contains("\\") ? Path.GetDirectoryName(Raw) : Raw; }
        }
        public override bool Equals(object obj)
        {
            var o = obj as ModuleFile;
            if(o == null)
                return false;

            return String.Compare(o.Raw, Raw, true) == 0;
        }
        public override int GetHashCode()
        {
            return Raw.ToLower().GetHashCode();
        }

        public void Reset(string path)
        {
            Raw = path;
            VerifyExist();
        }

        public static bool Exists(string win32Path)
        {
            return new ModuleFile(win32Path).Exist;
        }

        public bool Exist
        {
            get { return File.Exists(ValidPath); }
        }
        #region File Type Analysis

        public enum ModuleType
        {
            NotSet, Com, Il, Native, Unknown
                
        }

        private ModuleType getBinaryType()
        {
            var file = ValidPath;

            if (file.ToLower().EndsWith("dll"))
            {
                try
                {
                    Assembly.LoadFile(file);
                    return ModuleType.Il;
                }
                catch (Exception)
                {
                    var hModule = API.LoadLibraryEx(file, IntPtr.Zero,
                                                    API.LoadLibraryFlags.DONT_RESOLVE_DLL_REFERENCES);
                    if (hModule == IntPtr.Zero)
                    {
                        return ModuleType.Unknown;
                    }
                    API.FreeLibrary(hModule);

                    try
                    {
                        ITypeLib typeLib;
                        API.LoadTypeLibEx(file, API.RegKind.RegKind_None, out typeLib);
                        Marshal.ReleaseComObject(typeLib);
                        return ModuleType.Com;
                    }
                    catch
                    {
                        return ModuleType.Native;
                    }
                }
            }

            return file.ToLower().EndsWith("ocx") || file.ToLower().EndsWith("tlb")
                       ? ModuleType.Com
                       : ModuleType.Unknown;
        }

        public string Dir
        {
            get { return Path.GetDirectoryName(ValidPath); }
        }

        #endregion
    }
}